import { Quaternion, Vector3 } from "@babylonjs/core/Maths/math.vector";
import { Scene } from "@babylonjs/core/scene";
import { AnimationFrame, TransformNode, Utils } from "babylon-lib/index";
import { SoldierActionLogic } from './soldier-action-logic';
import { Model } from "./model";
import { WeightedAction } from 'babylon-lib/animation/weighted-action';
import { Random } from "babylon-lib/tool/random";
import { Axis } from "@babylonjs/core/Maths/math.axis";
import { SoldierManager } from "./soldier-manager";
import * as Data from '../../data/data';


interface ISoldierOptions {
    //
}


class Soldier {

    private _options: ISoldierOptions;
    private _direction = new Vector3(0, 0, 1);
    private _healthy = 1;
    private _actionLogic!: SoldierActionLogic;
    private _actions: WeightedAction[] = [];
    private _req = {};
    private _turnReq = {};
    private _moveReq = {};
    private _tarSpeed = 0;
    private _curSpeed = 0;

    public readonly name: string;
    public readonly id = Utils.id;
    public readonly scene: Scene;
    public readonly entity: TransformNode;

    constructor(name: string, scene: Scene, options: ISoldierOptions) {
        this.name = name;
        this.scene = scene;
        this._options = options;
        this.entity = new TransformNode(name + '-entity', scene);
        this.entity.rotationQuaternion = Quaternion.Identity();
        this.initializeModel();
        this.initializeActionLogic();
    }

    public get alive(): boolean {
        return this._healthy > 0;
    }

    public get direction(): Vector3 {
        return this._direction;
    }

    public damage(value: number): void {
        this._healthy -= value;
        if (this._healthy <= 0) {
            this._actionLogic.active(false);
            this.fall();
            SoldierManager.updateAlivedSoldiers();
            Data.enemyNumber.value++;
        }
    }

    public turn(direction: Vector3): void {
        AnimationFrame.cancel(this._turnReq);
        const curRot = this.entity.rotationQuaternion!;
        const tarRot = Quaternion.Identity();
        Quaternion.FromUnitVectorsToRef(Axis.Z, direction, tarRot);
        let t = 0;
        let spd = 0.01;
        AnimationFrame.request(() => {
            t += spd;
            Quaternion.SlerpToRef(curRot, tarRot, t, this.entity.rotationQuaternion!);
            this._direction.copyFrom(Axis.Z).applyRotationQuaternionInPlace(this.entity.rotationQuaternion!);
            if (t > 1) {
                return true;
            }
        }, 16, 200, this._turnReq);
    }

    public walk(): void {
        this._tarSpeed = 0.06;
        this.transmit(0);
        this.move(true);
    }

    public stay(): void {
        this._tarSpeed = 0;
        this.transmit(2);
    }

    public run(): void {
        this._tarSpeed = 0.2;
        this.transmit(1);
        this.move(true);
    }

    public fall(): void {
        this._tarSpeed = 0;
        this.transmit(Random.select(3, 4, 5));
    }

    private initializeActionLogic(): void {
        this._actionLogic = new SoldierActionLogic(this.name + '-action-logic', this, {});
        this._actionLogic.active(true);
    }

    private move(start: boolean): void {
        if (!start) {
            AnimationFrame.cancel(this._moveReq);
            return;
        }
        let cur = this._curSpeed;
        if (AnimationFrame.isRunning(this._moveReq)) return;
        let t = 0;
        const spd = 0.01;
        const ofs = new Vector3();
        AnimationFrame.request(() => {
            t = Math.min(1, t + spd);
            this._curSpeed = (this._tarSpeed - cur) * t + cur;
            ofs.copyFrom(this._direction).scaleInPlace(this._curSpeed);
            this.entity.position.addInPlace(ofs);

            if (this._curSpeed <= 0) return true;
        }, 16, 200, this._moveReq);
    }

    private transmit(index: number): void {
        AnimationFrame.cancel(this._req);
        this._actions[index].start();
        let count = 0;
        let speed = 0.04;
        AnimationFrame.request(() => {
            for (let i = 0; i < this._actions.length; i++) {
                const wa = this._actions[i];
                if (i === index) {
                    wa.weight += speed;
                } else {
                    wa.weight -= speed;
                    if (wa.weight <= 0) {
                        wa.weight = 0;
                        wa.stop();
                    }
                }
            }

            count += speed;
            if (count > 1) return true;
        }, 16, 200, this._req);
    }

    private initializeModel(): void {
        const model = Model.getSoldierModel();
        (model.rootNodes[0] as TransformNode).setParent(this.entity);

        const ag = model.animationGroups[0];
        ag.stop();

        const walk = new WeightedAction(ag, { from: 40, to: 120, loop: true, weight: 0, speedRatio: 0.9, useDirectAnimation: true });
        const run = new WeightedAction(ag, { from: 160, to: 240, loop: true, weight: 0, speedRatio: 1.8, useDirectAnimation: true });
        const idle = new WeightedAction(ag, { from: 280, to: 300, loop: true, weight: 0, speedRatio: 0.3, useDirectAnimation: true });
        const fall0 = new WeightedAction(ag, { from: 340, to: 360, loop: true, weight: 0, speedRatio: 1, useDirectAnimation: true });
        const fall1 = new WeightedAction(ag, { from: 400, to: 420, loop: true, weight: 0, speedRatio: 1, useDirectAnimation: true });
        const fall2 = new WeightedAction(ag, { from: 460, to: 480, loop: true, weight: 0, speedRatio: 1, useDirectAnimation: true });

        this._actions.push(walk, run, idle, fall0, fall1, fall2);
    }

}


export { Soldier };
export type { ISoldierOptions };